package org.jslaughter.component.physics.motion;

import java.util.Random;

import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.World;
import org.jslaughter.component.ai.Behaviour;
import org.nvframe.component.AbstractComponent;
import org.nvframe.component.physics.state.PhysicsPosition;
import org.nvframe.component.state.Position;
import org.nvframe.entity.Entity;
import org.nvframe.event.EventService;
import org.nvframe.event.eventtype.UpdateEvent;
import org.nvframe.event.eventtype.UpdateListener;
import org.nvframe.exception.NVFrameException;
import org.nvframe.manager.RegisterManager;
import org.nvframe.util.settings.SettingsObj;

/**
 * 
 * @author Nik Van Looy
 */
public class FollowMotion extends AbstractComponent implements UpdateListener {

	private float stepSpeed;
	private float rotSpeed;
	private Entity target;
	private float lockOnRadius;
	private int checkTargetMs;
	private int minChangeDirectionMs;
	private int maxChangeDirectionMs;
	
	private int currCheckTargetMs;
	private int currChangeDirectionMs;
	private float destDirection;
	private boolean lockedOn = false;
	private World world;

	public FollowMotion(String id, Entity owner, SettingsObj settings) throws NVFrameException {
		super(id, owner);
		
		stepSpeed = settings.getFloat("stepSpeed");
		rotSpeed = settings.getFloat("rotSpeed");
		lockOnRadius = settings.getFloat("lockOnRadius", 150f);
		minChangeDirectionMs = settings.getInt("minChangeDirectionMs", 2000);
		maxChangeDirectionMs = settings.getInt("maxChangeDirectionMs", 3000);
		currCheckTargetMs = checkTargetMs = settings.getInt("checkTargetMs", 1000);
		
		calculateNextChangeDirectionMs();
	}

	public void init() {
		Position pos = (Position) owner.getComponent(Position.class);
		destDirection = pos.getDirection();
		EventService.getInstance().addEventListener(this);
		
		world = (World) RegisterManager.getInstance().getOption("world");
	}
	
	private int calculateNextChangeDirectionMs() {
		Random generator = new Random();
		return generator.nextInt(maxChangeDirectionMs) + minChangeDirectionMs;
	}
	
	/**
	 * Calculates the degrees of angle he must move
	 * depending on delta and rotationSpeed
	 * 
	 * @param currentDir
	 * @param destDir
	 * @param delta
	 * @return
	 */
	private float calculateDirection(float currentDir, float destDir, int delta) {
		boolean clockwise = true;

		currentDir = currentDir % 360 < 0 ? 360 - Math.abs(currentDir % 360) : Math.abs(currentDir % 360);
		destDir = destDir % 360 < 0 ? 360 - Math.abs(destDir % 360) : Math.abs(destDir % 360);
		
		if(Math.abs(currentDir - destDir) <= rotSpeed)
			return currentDir;
		
		clockwise = destDir - currentDir < 0 ? false : true;
		
		if(Math.abs(destDir - currentDir) > 180)
			clockwise = !clockwise;
		
		if(clockwise)
			return currentDir + rotSpeed * delta;
		else
			return currentDir - rotSpeed * delta;
	}
	
	private Entity calculateTarget() {

		Position positionComp = (Position) owner.getComponent(Position.class);
		Behaviour behaviourComp = (Behaviour) owner.getComponent(Behaviour.class);
		
		for (Body body = world.getBodyList(); body.getNext() != null; body = body.getNext()) {
			if (body.isBullet())
				continue;
			
			// check behaviour between the two entities
			Entity target = (Entity) body.getUserData();
			if(!behaviourComp.isAggressiveTo(target))
				continue;
			
			Position targetPositionComp = (Position) target.getComponent(Position.class);
			
			float distance = (float) Math.sqrt(Math.pow(targetPositionComp.getX() - positionComp.getX(), 2) + 
					Math.pow(targetPositionComp.getY() - positionComp.getY(), 2));
	
			// distance too far => no aggression
			if(distance >= lockOnRadius)
				continue;
			
			lockedOn = true;
			
			/*if(ConfigManager.getInstance().getActive("debugMode"))
				System.out.println("entity lockon: " + owner + " to " + target + ", distance: " +  distance + " px");*/
			
			return target;
		}
		
		return null;
	}

	@Override
	public void onUpdate(UpdateEvent event) {
		PhysicsPosition positionComp = (PhysicsPosition) owner.getComponent(PhysicsPosition.class);

		int delta = event.getDelta();
		
		currChangeDirectionMs -= delta;
		currCheckTargetMs -= delta;
		
		// check for other targets?
		if(currCheckTargetMs <= 0) {
			currCheckTargetMs = checkTargetMs;
			target = calculateTarget();
		}
		
		// calculate direction
		if(currChangeDirectionMs <= 0 || lockedOn) {
			currChangeDirectionMs = calculateNextChangeDirectionMs();
			
			if(lockedOn && target != null && target.isEnabled() /*|| chance <= 4*/) {
				Position targetPositionComp = (Position) target.getComponent(Position.class);
				
				destDirection = (float) Math.toDegrees(Math.atan2(targetPositionComp.getY() - positionComp.getY(), 
						targetPositionComp.getX() - positionComp.getX()));
			}
			else/* if(chance > 7)*/ {				
				lockedOn = false;
				Random generator = new Random();
				
				if(generator.nextInt(2) == 0)
					destDirection += (float) (generator.nextFloat() * 90) + 1;
				else
					destDirection -= (float) (generator.nextFloat() * 90) + 1;
			}
		}

		// set velocity so owner moves to targetEntity's position
		//position.adjustVelocity(0, 0);
		positionComp.applyForce((float) (Math.cos(Math.toRadians(positionComp.getDirection())) * stepSpeed * delta), 
				(float) (Math.sin(Math.toRadians(positionComp.getDirection())) * stepSpeed * delta));
		
		// change direction of owner's render
		positionComp.setDirection(calculateDirection(positionComp.getDirection(), destDirection, delta));
	}

}